#!/usr/bin/env bash

function output_entry() {
	echo "$1=$2"
}

function parse_array() {
	local current_path="${1:+$1.}$2"
	local current_scope="root"
	local current_index=0

	while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
		[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
		preserve_current_char=0
		c=${c:-' '}

		case "$current_scope" in
			"root") # Waiting for new object or value
				case "$c" in
					'{')
						parse_object "$current_path" "$current_index"
						current_scope="entry_separator"
						;;
					']')
						return
						;;
					[\"tfTF\-0-9])
						preserve_current_char=1 # Let the parse value function decide what kind of value this is
						parse_value "$current_path" "$current_index"
						preserve_current_char=1 # Parse value has terminated with a separator or an array end, but we can handle this only in the next while iteration
						current_scope="entry_separator"
						;;
						
				esac
				;;
			"entry_separator")
				[ "$c" == "," ] && current_index=$((current_index+1)) && current_scope="root"
				[ "$c" == "]" ] && return
				;;
		esac
	done
}

function parse_value() {
	local current_path="${1:+$1.}$2"
	local current_scope="root"

	while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
		[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
		preserve_current_char=0
		c=${c:-' '}

		case "$current_scope" in
			"root") # Waiting for new string, number or boolean
				case "$c" in
					'"') # String begin
						current_scope="string"
						current_varvalue=""
						;;
					[\-0-9]) # Number begin
						current_scope="number"
						current_varvalue="$c"
						;;
					[tfTF]) # True or false begin
						current_scope="boolean"
						current_varvalue="$c"
						;;
					"[") # Array begin
						parse_array "" "$current_path"
						return
						;;
					"{") # Object begin
						parse_object "" "$current_path"
						return
				esac
				;;
			"string") # Waiting for string end
				case "$c" in
					'"') # String end if not in escape mode, normal character otherwise
						[ "$current_escaping" == "0" ] && output_entry "$current_path" "$current_varvalue" && return
						[ "$current_escaping" == "1" ] && current_varvalue="$current_varvalue$c" && current_escaping=0
						;;
					'\') # Escape character, entering or leaving escape mode
						current_escaping=$((1-current_escaping))
						current_varvalue="$current_varvalue$c"
						;;
					*) # Any other string character
						current_escaping=0
						current_varvalue="$current_varvalue$c"
						;;
				esac
				;;
			"number") # Waiting for number end
				case "$c" in
					[,\]}]) # Separator or array end or object end
						output_entry "$current_path" "$current_varvalue"
						preserve_current_char=1 # The caller needs to handle this char
						return
						;;
					[\-0-9.]) # Number can only contain digits, dots and a sign
						current_varvalue="$current_varvalue$c"
						;;
					# Ignore everything else
				esac
				;;
			"boolean") # Waiting for boolean to end
				case "$c" in
					[,\]}]) # Separator or array end or object end
						output_entry "$current_path" "$current_varvalue"
						preserve_current_char=1 # The caller needs to handle this char
						return
						;;
					[a-zA-Z]) # No need to do some strict checking, we do not want to validate the incoming json data
						current_varvalue="$current_varvalue$c"
						;;
					# Ignore everything else
				esac
				;;
		esac
	done
}

function parse_object() {
	local current_path="${1:+$1.}$2"
	local current_scope="root"

	while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
		[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
		preserve_current_char=0
		c=${c:-' '}

		case "$current_scope" in
			"root") # Waiting for new field or object end
				[ "$c" == "}" ]  && return
				[ "$c" == "\"" ] && current_scope="varname" && current_varname="" && current_escaping=0
				;;
			"varname") # Reading the field name
				case "$c" in
					'"') # String end if not in escape mode, normal character otherwise
						[ "$current_escaping" == "0" ] && current_scope="key_value_separator"
						[ "$current_escaping" == "1" ] && current_varname="$current_varname$c"
						;;
					'\') # Escape character, entering or leaving escape mode
						current_escaping=$((1-current_escaping))
						current_varname="$current_varname$c"
						;;
					*) # Any other string character
						current_escaping=0
						current_varname="$current_varname$c"
						;;
				esac
				;;
			"key_value_separator") # Waiting for the key value separator (:)
				[ "$c" == ":" ] && parse_value "$current_path" "$current_varname" && current_scope="field_separator"
				;;
			"field_separator") # Waiting for the field separator (,)
				[ "$c" == ',' ] && current_scope="root"
				[ "$c" == '}' ] && return
				;;
		esac
	done
}

function parse() {
	chars_read=0
	preserve_current_char=0

	while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
		read -r -s -n 1 c
		c=${c:-' '}
		chars_read=$((chars_read+1))

		# A valid JSON string consists of exactly one object
		[ "$c" == "{" ] && parse_object "" "" && return
        # ... or one array
        [ "$c" == "[" ] && parse_array "" "" && return
        
	done
}

if [ -z "$@" ]; then
	INPUT=$(cat -)
else
	INPUT=$(echo "$@")
fi

INPUT_LENGTH="${#INPUT}"
parse "" "" <<< "${INPUT}"

